package org.javason.util;

import java.util.HashSet;
import java.util.Random;

import org.apache.log4j.Logger;
import org.javason.test.ElapsedTime;

public class Metrics {
	public static final String VERSION = "$Rev: 63219 $";
	public static final String FORMAT = "[%s.%s]";
	private static final String LABEL = "[Metrics]";
	private static final String DELIMTER = "|";
	private static final Logger LOG = Logger.getLogger(Metrics.class);
	private static Double _threshold = null;
	
	private ElapsedTime _timer = null;
	private static Tracker _tracker = new Tracker();
	
	protected Metrics() {}
	
	public static Metrics beginCollection() {
		Metrics metrics = new Metrics();
		metrics.beginTimer();
		return metrics;
	}

	public void endCollection() {
		_timer.end();
		_tracker.end(this);
	}
	
	public void endAndLogCollection() {
		StackTraceElement stackElement = (new Throwable()).getStackTrace()[1];
		endAndLogCollection(FORMAT, stackElement.getClassName(), stackElement.getMethodName());
	}
	
	public void endAndLogCollection(String format, Object ... args) {
		endCollection();
		log(format, args);
	}
	
	public void endCollectionAndLogCurrentMethod() {
		StackTraceElement stackElement = (new Throwable()).getStackTrace()[1];
		endAndLogCollection(FORMAT, stackElement.getClassName(), stackElement.getMethodName());
	}
	
	public void endCollectionAndLogCurrentMethod(String extraInfo) {
		StackTraceElement stackElement = (new Throwable()).getStackTrace()[1];
		endCollection();
		logMethod(stackElement.getClassName(), stackElement.getMethodName(), extraInfo);
	}
	
	public void log() {
		StackTraceElement stackElement = (new Throwable()).getStackTrace()[1];
		log(FORMAT, stackElement.getClassName(), stackElement.getMethodName());
	}
	
	public void log(String format, Object ... args) {
		double elapsedTime = getElaspedTimeSeconds();
		if (isThresholdExceeded(elapsedTime)) {
			StringBuilder output = new StringBuilder(LABEL);
			output.append(DELIMTER).append('[').append(_tracker.getId(this)).append(']');
			if (format != null) {
				output.append(DELIMTER).append(String.format(format, args));
			}
			output.append(DELIMTER).append(elapsedTime);
			LOG.error(output.toString());
		}
	}
	
	public void logMethodCall(String clazz, String method, String args) {
		logMethod(clazz, method, "args=" + args);
	}
	
	public void logMethod(String clazz, String method, String extraInfo) {
		double elapsedTime = getElaspedTimeSeconds();
		if (isThresholdExceeded(elapsedTime)) {
			StringBuilder output = new StringBuilder(LABEL);
			output.append(DELIMTER).append('[').append(_tracker.getId(this)).append(']');
			output.append(DELIMTER).append(String.format(FORMAT, clazz, method));
			output.append(DELIMTER).append(elapsedTime);
			output.append(DELIMTER).append(extraInfo);
			LOG.error(output.toString());
		}
	}
	
	public double getElaspedTimeSeconds() {
		return (_timer != null ? _timer.getElaspedTimeSeconds() : -1);
	}
	
	public void restartCollection() {
		endCollection();
		beginTimer();
	}

	
	
	
	protected void beginTimer() {
		_tracker.add(this);
		_timer = new ElapsedTime();
		_timer.begin();
	}

	private boolean isThresholdExceeded(double elapsedTime) {
		if (_threshold == null) {
			_threshold = MetricsConfiguration.getGlobalMinThreshold();
		}

		return (_threshold == 0 || (elapsedTime > _threshold));
	}
	
	
	static class Tracker  {
		private static ThreadLocal _id = new ThreadLocal() {
			protected synchronized Object initialValue() { return null; }
		};
		
		private static ThreadLocal _metrics_ids = new ThreadLocal() {
			protected synchronized Object initialValue() { return new HashSet<Integer>(); }
		};
        
        
        public void add(Metrics metrics) {
        	HashSet<Integer> metricsIds = (HashSet<Integer>) _metrics_ids.get();
        	int metricsObjectId = metrics.hashCode();
        	long id = Thread.currentThread().getId();
        	if (!metricsIds.contains(metricsObjectId)) {
        		if (metricsIds.isEmpty()) {
        			_id.set(Integer.valueOf((new Random()).nextInt(100000)));
        		}
        		metricsIds.add(metricsObjectId);
        		_metrics_ids.set(metricsIds);
        	}
        }

        public int getId(Metrics metrics) {
        	HashSet<Integer> metricsIds = (HashSet<Integer>) _metrics_ids.get();
        	return (Integer)_id.get();
        }
        
        public void end(Metrics metrics) {
        	HashSet<Integer> metricsIds = (HashSet<Integer>) _metrics_ids.get();
        	metricsIds.remove(metrics.hashCode());
        }
    }
}
